Skip to content

Merge dev → main (automated) - #604

Merged
SorraTheOrc merged 28 commits into
mainfrom
release/dev-to-main-20260723213437
Jul 23, 2026
Merged

Merge dev → main (automated)#604
SorraTheOrc merged 28 commits into
mainfrom
release/dev-to-main-20260723213437

Conversation

@SorraTheOrc

Copy link
Copy Markdown
Member

Automated release created by ship skill.\n\nIncludes CHANGELOG.md with work-item summaries from this release.

Sorra the Orc added 28 commits July 21, 2026 00:17
…H000J37N)

Implements the 'Sell cards in Main Street' feature allowing players to sell
placed business and community-space cards during the MarketPhase.

Key changes:
- Add soldSlots: boolean[] to MainStreetState and MainStreetSerializedState
- Add sellBusiness() to MainStreetMarket with Math.ceil(50% refund) formula
- Add canSellBusiness() legality check (MarketPhase only, not in placement mode)
- Add executeSell() to MainStreetEngine for action dispatch
- Add sellBusinessCommand() to MainStreetCommands with snapshot-based undo
- Add totalUpgradeCost tracking on BusinessCard for accurate refunds
- Update computeIncome, computeSynergyBonus, computeReputationPerTurn,
  computeSynergyPairs, applyIncome to exclude sold slots
- Update computeHandCardSynergyBonus to skip sold slots
- Add sell click handler on placed cards in MainStreetRenderer
- Add dimmed 'SOLD' overlay for sold cards in renderer
- Add showSellConfirmation overlay dialog with Sell/Cancel buttons
- Add onSellCard() handler in MainStreetTurnController
- Add migration support for legacy saves without soldSlots
- Add comprehensive test suite (25 tests covering all ACs)

Acceptance criteria:
AC1: Click placed card opens sell dialog with card info + Sell/Cancel buttons
AC2: Math.ceil((purchasePrice + upgradeCosts) / 2) refund
AC3: Card stays on grid with dimmed visual, no income/synergy/reputation
AC4: Undoable via existing undo system
AC5: Upgrades lost (included in refund calculation)
AC6: Non-functional for all game calculations
AC7: Only during MarketPhase, not in card-placement mode
AC8: Full test suite passes
AC9: Build succeeds with no errors

Closes CG-0MQOA5U4H000J37N
…logs (CG-0MRP1AY0L008J5VL)

Adds a new 'UI Best Practices: Creating Modal Dialogs' section to
AGENTS.md documenting the correct pattern for creating overlay dialogs,
using the sell confirmation dialog as the reference implementation.

Key lessons documented:
- Use createOverlayBackground / createOverlayButton / dismissOverlay from @ui
- CRITICAL: Parent ALL overlay text/buttons into hudContainer
- Depth ordering (backdrop 199 < box 200 < text/buttons 201)
- Cleanup pattern with dismissOverlay
- Reference to showSellConfirmation for a complete example

Closes CG-0MRP1AY0L008J5VL
…G-0MQN31CD400709UC)

The game-over overlay in Feudalism unconditionally displayed
"Tiebreak: fewest cards wins" even when the winner had a clear
influence lead. This was confusing to players.

Changes:
- FeudalismOverlays.ts: conditionally append tiebreaker text only
  when humanInfluence === aiInfluence
- FeudalismGameOverOverlay.test.ts: unit tests verifying tiebreaker
  display logic and getWinnerIndex consistency

ACs verified:
1. Tiebreaker text hidden on non-tie wins ✓
2. Tiebreaker text shown on tie wins ✓
3. Score summary unchanged ✓
4. No regression on winner determination ✓
5. All 240 test files pass (4293 tests) ✓
6. Build succeeds ✓
…TVIH0052HW0)

Changes:
- Add OVERLAY_DEPTH (2000) and OVERLAY_BG_ALPHA (0.01) constants
  to FeudalismConstants.ts (following Beleaguered Castle pattern)
- Pass background depth 2000 to createGameOverOverlay in
  FeudalismOverlays.ts, matching the standard 'game-over' overlay
  type (depth 2000) instead of the default depth 10
- Update z-order test docs to reflect new overlay depth range

All ACs met:
1. Overlay uses depth 2000 (was defaulting to depth 10)
2. Standard button positions (already correct via createGameOverOverlay)
3. Content display preserved (no text/layout changes)
4. Transcript saving preserved (no behavioral change)
5. Full test suite passes (4293 tests, 0 failures)
6. Documentation updated (z-order test, constants with comments)
…ushi Go

When reduced motion is enabled, animations are skipped which can make the
AI's turn feel instantaneous and lacking perceptible thinking time.

Changes per game:
- Golf (GolfAiController): adds SWAP_ANIM_DURATION (450ms) extra delay
- Feudalism (FeudalismTurnController): adds MOVE_DURATION (700ms) extra delay
- Lost Cities (LostCitiesTurnController): adds ANIM_DURATION (300ms) extra delay
- Sushi Go (SushiGoScene): adds ANIM_DURATION (300ms) extra delay

Each game's scene propagates the settingsPanel reducedMotion preference
to its AI controller/turn controller, which uses it to conditionally
increase the AI delay to match the skipped animation duration.

Closes CG-0MQPSVDTD002JCX5
…40072RL9)

- Add new ms-icon-stats.svg 16x16 bar chart icon following the existing
  icon visual pattern (colored background + white semi-transparent bars)
- Add 'stats' to the preloaded icons list in MainStreetLifecycleManager
- Update StatsButton to display the SVG icon via Phaser Image when the
  ms-icon-stats texture is available, with graceful fallback to the Greek
  Sigma Σ text label when the texture is not loaded
- Hover effects updated: tint the icon white (or text white) on hover,
  restore gold tint (or text color) on pointer out
- Write tests: SVG asset validation, preload integration, StatsButton
  class structure with icon/fallback references
…ing (CG-0MQR54BSD007FQTA)

Add fetch-based pre-check in loadMainStreetTfModule() to verify the synth
module exists before attempting dynamic import(), avoiding the Chromium
'Failed to load module script' console error when the file is missing.

Changes:
- Add checkSynthModuleExists() helper that fetches the module URL and
  detects HTML fallback responses (Vite dev server behavior for missing
  paths)
- Log clear [MainStreet] console.warn with instructions to run
  'npm run tf:generate' when module is not found
- Update TfGeneratedModule cached type to accept null for caching the
  'not found' state
- Add two new tests: (1) HTML response returns null with warning,
  (2) network error falls through gracefully
- Update docs/DEVELOPER.md with ToneForge quick-start note and
  graceful degradation documentation

Closes CG-0MQR54BSD007FQTA
The substantive fix (reducing HUD strip width from 66% to 50%) was already
applied by CG-0MQU3AREL0099R1I (commit 0ba4c3b). This change only fixes
the stale comment that still said "2/3 width".
Implements the rule that synergy bonuses (coin and reputation) only apply
between businesses of **different base types** (template IDs). Same-type
adjacent businesses:
- Receive 0 synergy from each other (both coin and reputation)
- Have their base income reduced to 60% (applied once, not stacked)

Changes:
- Added getBaseTypeId() helper in MainStreetCards.ts — strips the serial
  suffix (-N) from card IDs to identify the base template
- Added hasAdjacentSameType() helper in MainStreetAdjacency.ts — checks
  if a business has at least one adjacent same-type neighbor
- Modified computeSynergyBonus() — skips same-type neighbors
- Modified computeSynergyRepBonus() — skips same-type neighbors
- Modified computeBusinessIncome() — applies 0.6 multiplier when adjacent
  to same-type business
- Modified computeIncome() — applies the same 0.6 multiplier in breakdown
- Modified computeSynergyPairs() — excludes same-type pairs from visual
  synergy lines
- Updated clinic-health-synergy.test.ts — adjusted expected rep values
  for same-type Clinic adjacency (synergy rep nullified)
- Added same-type-synergy.test.ts — comprehensive tests covering all ACs
- Updated docs/main-street/core-rules-and-mechanics.md
Refactors drawMarketRow to accept an optional alignmentStartX parameter.
When rendering the investments row, passes the development row's startX
so investment card slots (3 total) sit at grid columns 0, 1, 2 —
directly below the first three development cards.

Changes:
- MainStreetRenderer.ts: compute devStartX in refreshMarket(), pass
  to both drawMarketRow calls; drawMarketRow uses alignmentStartX
  when provided instead of independent centering.
- tests/main-street/market-row-alignment.test.ts: new test file with 7
  tests verifying the alignment math.
- Deleted orphaned scripts/generate-mind-sfx.mjs
- Deleted stale screenshots at tests/ui/__screenshots__/TheMindMigration.browser.test.ts/
- Updated docs/DEVELOPER.md: removed all The Mind references, genericized migration pattern section
- Updated README.md: removed The Mind from directory listing
- Updated docs/SFX_CONVENTION.md: removed the-mind from audio structure
- Updated docs/the-build/engine-capabilities-audit.md: removed The Mind section and genericized timer reference
- Updated public/assets/CREDITS.md: removed The Mind card and audio attribution sections
- Updated .pi-compact.md: removed TheMind from code-smell refactoring line
…i Go, Main Street, Lost Cities

Adds a [Menu] button to the game-over overlays of three games that had
custom overlays excluded from the shared createGameOverOverlay migration:

- Sushi Go: Added Menu button via createActionButton next to Play Again
- Main Street: Added Menu button via createOverlayButton next to Play Again
- Lost Cities: Added Menu button via createActionButton next to New Match

All Menu buttons dismiss the overlay and navigate to GameSelectorScene.

Added browser tests verifying Menu button existence and interactivity for
all three games.

Created child work items for migrating each custom overlay to the shared
createGameOverOverlay component.

ACs:
1. ✅ Each game displays a [Menu] button navigating to GameSelectorScene
2. ✅ Menu buttons styled consistently with existing overlay buttons
3. ✅ [Play Again] / [New Match] still works identically
4. ✅ Games using shared overlay (Golf, Beleaguered Castle, etc.) not modified
5. ✅ Child work items created for migration
6. ✅ Tests added for Menu button verification
7. ✅ Full test suite passes
Replaces monolithic per-turn income/reputation recalculation with per-card
cached effective values (currentIncome/currentReputationPerTurn) that are
incrementally updated when cards are placed, sold, or upgraded.

Changes:
- Added currentIncome and currentReputationPerTurn fields to BusinessCard
  and CommunitySpaceCard interfaces (optional, undefined until placed)
- Added syncCardCurrentIncome, syncCardCurrentRepPerTurn, recalculateCard,
  updateNeighborsOnPlacement, and updateNeighborsOnSale functions in
  MainStreetAdjacency.ts
- Modified applyIncome() to read cached values instead of recomputing from
  scratch each turn, with fallback to computation when cached values are
  undefined (legacy saves, direct grid manipulation)
- Modified purchaseBusiness, sellBusiness, and purchaseUpgrade in
  MainStreetMarket.ts to call incremental update functions
- Modified placeFromHand and sellFromTableau in MainStreetEngine.ts to call
  incremental update functions
- Added legacy migration (no explicit migration needed - undefined fields
  trigger fallback computation)
- Added incremental-income.test.ts with 28 tests covering field init,
  placement/sale recalculation, same-type penalty, save/load round-trip,
  and integration scenarios
- Updated community-space-types.test.ts fixture to include new fields
Root cause: Three overlapping sound paths fired the same SFX keys per
game action — event system (connectToEvents), flipCard sfx config, and
explicit soundManager.play calls in GolfAnimator tweens.

Fix:
- GolfScene: Remove card-movement events (card-drawn, card-flipped,
  card-swapped, card-discarded) from EventSoundMapping. The animation
  layer in GolfAnimator already plays the corresponding SFX.
- GolfAnimator: Remove all periodic/redundant sound calls
  - animateSwap: Remove move sfx from flipCard config and
    lastMove/onUpdate tracking from drawn-card tween
  - animateDiscardAndFlip: Remove start/move sfx from flipCard
    (discard was already played in animateDrawnCardToDiscard)
  - showDrawnCard (stock): Remove move sfx from flipCard config
  - showDrawnCard (discard): Remove onUpdate periodic CARD_DRAW calls
  - animateDrawnCardToDiscard: Remove onUpdate periodic and onComplete
    duplicate CARD_DISCARD calls

Each action now plays each unique SFX key at most once.
- Draw: CARD_DRAW (start) + CARD_FLIP (midpoint, stock only)
- Swap: CARD_SWAP (start) + CARD_FLIP (midpoint, grid card reveal)
- Discard-and-flip: CARD_DISCARD (discard phase) + CARD_FLIP (flip phase)
- Reduced motion preserved (no sounds when active)

Tests added: GolfSoundDuplication.test.ts (13 tests) verifying the
event mapping excludes card events and animator has no periodic
or duplicate sound calls.
… in PRD

Adds explicit documentation in prd-milestone-2.md that Clinic was
reclassified from Service to Health synergy, along with updated
references to 6 synergy types (was 5).

Closes CG-0MQRB9RMF003PRNO
…GSS0080N9P)

Replaces the fully duplicated Worklog rules section (~200 lines copied from
the global AGENTS.md at ~/.pi/agent/AGENTS.md) with a concise reference
section that points to the global file and summarizes only TCE-specific
conventions (prefix, priorities, stages).

Net reduction: 401 -> 224 lines (44% smaller).
- All project-specific sections preserved: Components, Directory Structure,
  Development Workflow, Technology Stack, Licensing, Doc-Update Policy,
  UI Best Practices: Creating Modal Dialogs
- Duplicated work-item tracking rules replaced with cross-reference
…ename Rep to Reputation

Per audit rejection (rgardler, 2026-07-22):
- Left-align Coins text in the HUD strip
- Right-align Score text in the HUD strip
- Center Reputation (renamed from 'Rep') text
- Update animation popup positions (coinX, repX) to match new layout
- Update browser test to check for 'Reputation:' prefix

CG-0MQS25QSO0031OHU
The HUD tooltip income display computed by buildCoinsTooltip() and
getIncomeResult() was not passing state.soldSlots to computeIncome(),
defaulting to an empty soldSlots array. This caused sold cards to
still appear as generating income in the HUD tooltip display, even
though applyIncome() correctly excluded them from actual game logic.

Fix: pass state.soldSlots to both computeIncome() calls so that sold
cards are excluded from the displayed total.

Test: added 'excludes sold cards from income display' test verifying
both computeIncome() and buildCoinsTooltip() return 0 after selling.

Work item: CG-0MQOA5U4H000J37N
…Income()

The HUD tooltips (buildCoinsTooltip, getIncomeResult) were calling
computeIncome() which does a full grid recalculation from scratch.
After CG-0MRV84ZT60069PW6, each card has a cached currentIncome field
that is incrementally updated on placement/sale/upgrade. The tooltips
now sum these cached values with a fallback to computeBusinessIncome
for cards without cached values (legacy saves, test setups).

This also correctly excludes sold card income: sold slots are skipped
during iteration, so the HUD display matches the actual income phase.

Work item: CG-0MQOA5U4H000J37N (audit gap)
Previously, applyIncome() and the HUD tooltips fell back to
computeBusinessIncome() when currentIncome was undefined on a card
(legacy saves). Per design decision: legacy saves are not supported.

Changes:
- applyIncome(): use card.currentIncome ?? 0 instead of falling back
  to computeBusinessIncome (same for reputation per turn)
- buildCoinsTooltip, getIncomeResult: use card.currentIncome ?? 0
- Removed unused computeBusinessIncome/bonusPerNeighbor imports
- Updated legacy migration test to expect zero income for cards
  without currentIncome
- Updated all tests that place cards on the grid to call
  recalculateCard before applyIncome

Work item: CG-0MQOA5U4H000J37N
… to percentage multipliers

Core changes:
- MainStreetAdjacency.ts: Changed  default from  to .
  Rewrote  to use percentage-based formula:
  synergy = effectiveBase * rate * bonusPerNeighbor * N
  where rate comes from the source card's synergyCoinBonus, not the neighbor's.
  Synergy-neutral cards (synergyCoinBonus=0 AND synergyRepBonus=0) are skipped
  in neighbor counting.
- MainStreetDifficulty.ts: Repurposed synergyBonusPerNeighbor as a multiplier:
  Easy=1.5, Medium=1.0, Hard=0.75. Updated JSDoc.
- MainStreetCards.ts: Updated SYNERGY_BONUS_PER_NEIGHBOR deprecated comment.
- card-data.csv: Changed synergyCoinBonus from 1 to 0.5 for biz-gallery, biz-spa,
  biz-clinic, biz-private-clinic, cs-library. Pawn Shop stays at 0.
- csv-checksum.json: Updated to reflect CSV changes.
- docs/main-street/core-rules-and-mechanics.md: Updated synergy description and
  income phase formula.
- monte-carlo-baseline.json: Regenerated with new balance.

Test updates:
- adjacency.test.ts: Updated expected values for percentage-based formula.
- same-type-synergy.test.ts: Updated makeBiz defaults to 0.5 and expected values.
- expanded-card-pool.test.ts: Updated Clinic and Cinema expected values (4→4.5).
- clinic-health-synergy.test.ts: Updated Private Clinic/Pharmacy synergy values.
- MainStreetHandSynergy.test.ts: Updated makeBiz default from 1 to 0.5.
@SorraTheOrc
SorraTheOrc merged commit 32660b7 into main Jul 23, 2026
1 check passed
@SorraTheOrc
SorraTheOrc deleted the release/dev-to-main-20260723213437 branch July 23, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant